package com.fy.login.internal;

import com.fy.login.*;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.util.*;

/**
 * 抽象 PrincipalModule
 * @author zhufeifei 2022/8/9
 * @see SimpleLoginModule
 * @implSpec 需在 login 的最后一步执行 {@literal return checkLogin()}，在 logout 执行 {@literal handlerLogout()}
 **/

public abstract class AbstractLoginModule implements LoginModule, LoginConstants {

    private final IUserService userService;
    private final ValidateChains validateChains;
    private final EventListeners eventListeners;
    protected CallbackHandler callbackHandler;
    protected Subject subject;
    protected Map<String,?> options;

    private boolean successed;

    public AbstractLoginModule() throws LoginException {
        ServiceLoader<IUserService> userServiceServiceLoader = ServiceLoader.load(IUserService.class);
        Optional<IUserService> optional = userServiceServiceLoader.findFirst();
        if (optional.isPresent()) {
            this.userService = optional.get();
        }
        else {
            throw new LoginException(NON_USER_SERVICE);
        }
        validateChains = new ValidateChains();
        eventListeners = EventListeners.getInstance();
    }

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.options = options;
        this.successed = false;
    }

    @Override
    public boolean commit() throws LoginException {
        if (successed) {
            MetadataCallback<?> metadataCallback = new MetadataCallback<>();
            handlerCallback(this.callbackHandler,metadataCallback);
            BaseMetadata metadata = metadataCallback.getMetadata();
            List<IEventListener> events = this.eventListeners.getEvents(EventListeners.Type.LOGIN);
            for (IEventListener e : events) {
                e.action(metadata, this.subject);
            }
            // 写入权限
            subject.getPrincipals().add(new SimplePrincipal(metadata.getRole().toString().toLowerCase(Locale.ROOT)));
        }
        return true;
    }

    @Override
    public boolean abort() throws LoginException {
        System.out.println("验证失败");
        return true;
    }

    /**
     * 检查登陆事件
     * @param metadata 元数据
     * @param password 密码
     * @return boolean
     * @throws LoginException 登陆异常
     */
    protected boolean checkLogin(BaseMetadata metadata, char[] password) throws LoginException{
        this.validateChains.checkAll(metadata, this.subject);
        boolean flag = Arrays.equals(password, userService.getPwd(metadata.getUsername()));
        if (flag) {
            successed = true;
        }
        return flag;
    }

    /**
     * 处理登出事件
     * @param metadata 元数据
     */
    protected void handlerLogout(BaseMetadata metadata) {
        List<IEventListener> events = this.eventListeners.getEvents(EventListeners.Type.LOGOUT);
        for (IEventListener e : events) {
            e.action(metadata, this.subject);
        }
    }

    /**
     * 处理 Callback 回调
     * @param callbacks callback回调列表
     * @throws LoginException 登陆异常
     */
    public static void handlerCallback(CallbackHandler callbackHandler, Callback... callbacks) throws LoginException {
        try {
            callbackHandler.handle(callbacks);
        } catch (IOException e) {
            LoginException e1 = new LoginException(IO_EXCEPTION);
            e1.initCause(e);
            throw e1;
        } catch (UnsupportedCallbackException e) {
            LoginException e1 = new LoginException(NON_SUPPORT_CALLBACK);
            e1.initCause(e);
            throw e1;
        }
    }

}
